package cz.cuni.mff.ufal.volkapp;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import cz.cuni.mff.ufal.volk.ApplicationSettings;
import cz.cuni.mff.ufal.volk.Component;
import cz.cuni.mff.ufal.volk.services.Services;

public class Application implements Runnable, Closeable {

	static {
		// has to be performed before any other action:
		configureLogging();
	}

	private static Application currentApplication;
	private final static Object currentApplicationLock = new Object();

	public static Application getApplication() {
		if (currentApplication == null) {
			synchronized (currentApplicationLock) {
				if (currentApplication == null)
					currentApplication = new Application();
			}
		}
		return currentApplication;
	}

	private Application() {
		context = new FileSystemXmlApplicationContext(CONFIG);
		Services.setFactory(context);
		ApplicationSettings.setFactory(context);
	}

	private static Logger log = Logger.getLogger(Application.class);
	private final ConfigurableApplicationContext context;
	private State state = State.CREATED;
	private final Object lock = new Object();

	@Override
	public void run() {
		synchronized (lock) {
			if (state != State.CREATED) {
				throw new IllegalStateException("The server is not in CREATED state");
			}
			state = State.RUNNING;

			// it must be checked whether an alternative logging configuration should be
			// used
			if (context.containsBean(BEAN_LOG4J)) {
				String path = context.getBean(BEAN_LOG4J, String.class);
				PropertyConfigurator.configure(path);
			}

			try {

				// an exception will be thrown in the next line if the bean cannot be
				// created
				@SuppressWarnings("unchecked")
        List<Component> components = context.getBean(BEAN_COMPONENTS, List.class);
				for (Component component : components) {
					component.open();
				}

			} catch (IOException e) {
				log.fatal("An IOException occurred", e);
			}

			// the executing thread will wait for termination
			try {
	      lock.wait();
      } catch (InterruptedException e) {
	      log.fatal("An InterruptedException occurred", e);
      }
		}
	}

	/**
	 * Tries to terminate the application. The method can be called multiple times. Any attempt to call
	 * the method after it has successfully returned has no effect.
	 */
	@Override
	public void close() {
		synchronized (lock) {

			if (state != State.RUNNING) {
				return; // we don't want to throw an exception
			}

			lock.notify();

			context.close();
			state = State.CLOSED;

		}
	}

	public final static String BEAN_LOG4J = "log4j";
	public final static String BEAN_COMPONENTS = "components";
	public final static String DEFAULT_LOG4J = "volkapp.log4j.txt";
	public final static String CONFIG = "volkapp.config.xml";

	/**
	 * Performs default logging configuration.
	 */
	private static void configureLogging() {
		File defaultLog4j = new File(DEFAULT_LOG4J);
		if (defaultLog4j.isFile()) {
			try {
				PropertyConfigurator.configure(DEFAULT_LOG4J);
			} catch (Exception e) {
				BasicConfigurator.configure();
				log.error("Incorrect logging configuration: " + DEFAULT_LOG4J,
				    e);
			}
		} else {
			BasicConfigurator.configure();
		}
	}

	public final State getState() {
		// does not have to be synchronized
		return state;
	}

	public static enum State {
		CREATED,
		RUNNING,
		CLOSED,
	}

}
